use std::ops::{Range, RangeInclusive};

pub enum CharPattern {
    One(char),
    Any,
    Range(Range<char>),
    RangeInclusive(RangeInclusive<char>),
    Whitespace,
    Alphanumeric,
    Not(Box<CharPattern>),
}

impl CharPattern {
    fn match_char(&self, that: char) -> bool {
        use CharPattern as E;

        match self {
            E::One(c) => c == &that,
            E::Any => true,
            E::Range(range) => range.contains(&that),
            E::RangeInclusive(range_inclusive) => range_inclusive.contains(&that),
            E::Whitespace => that.is_ascii_whitespace(),
            E::Alphanumeric => that.is_ascii_alphanumeric(),
            E::Not(pattern) => !pattern.match_char(that),
        }
    }
}

pub enum Pattern {
    Char(CharPattern),
    String(String),
    Or(Vec<Pattern>),
    Then(Vec<Pattern>),
    Multiple0(Box<Pattern>),
    Multiple1(Box<Pattern>),

    /// ```
    /// use sre::{IntoPattern, then};
    ///
    /// assert_eq!(
    ///     then!('a', 'x'.into_pattern().optional(), 'b')
    ///         .match_prefix("abc"),
    ///     (true, "c")
    /// )
    /// ```
    Optional(Box<Pattern>),

    /// ```
    /// use sre::{CharPattern, IntoPattern};
    ///
    /// assert_eq!(
    ///     CharPattern::Any
    ///         .into_pattern()
    ///         .repeat(10)
    ///         .match_prefix("0123456789_"),
    ///     (true, "_")
    /// )
    /// ```
    Repeat {
        pattern: Box<Pattern>,
        n: usize,
    },
}

#[macro_export]
macro_rules! or {
    ( $( $pattern:expr ),* $(,)?) => {
        $crate::Pattern::Or(vec![
            $(
                $crate::IntoPattern::into_pattern($pattern)
            ),*
        ])
    };
}

#[macro_export]
macro_rules! then {
    ( $( $pattern:expr ),* $(,)?) => {
        $crate::Pattern::Then(vec![
            $(
                $crate::IntoPattern::into_pattern($pattern)
            ),*
        ])
    };
}

#[test]
fn test_ident() {
    let p = then!(
        or!('a'..='z', 'A'..='Z', '_'),
        or!('a'..='z', 'A'..='Z', '0'..='9', '_').multiple0(),
    );

    assert!(p.match_all("f"));
    assert!(p.match_all("foo"));
    assert!(p.match_all("bar01"));
    assert!(p.match_all("baz____123"));
    assert!(p.match_all("__a"));

    assert!(!p.match_all(""));
    assert!(!p.match_all("0"));
    assert!(!p.match_all("ab)xx"));
}

#[test]
fn test_basic() {
    assert_eq!(('a').into_pattern().match_prefix("abc"), (true, "bc"));
    assert_eq!(('a').into_pattern().match_prefix("xbc"), (false, "xbc"));
    assert_eq!(('0'..='9').into_pattern().match_prefix("012"), (true, "12"));
    assert_eq!(
        ('0'..='9').into_pattern().match_prefix("x12"),
        (false, "x12")
    );

    assert_eq!(
        ('0'..='9').into_pattern().multiple0().match_prefix("0123"),
        (true, ""),
    );

    assert_eq!(
        ('0'..='9').into_pattern().multiple0().match_prefix("x0123"),
        (true, "x0123"),
    );

    assert_eq!(
        ('0'..='9').into_pattern().multiple1().match_prefix("0123"),
        (true, ""),
    );

    assert_eq!(
        ('0'..='9').into_pattern().multiple1().match_prefix("x0123"),
        (false, "x0123"),
    );
}

pub trait IntoPattern {
    #[must_use]
    fn into_pattern(self) -> Pattern;
}

impl IntoPattern for CharPattern {
    fn into_pattern(self) -> Pattern {
        Pattern::Char(self)
    }
}

impl IntoPattern for Pattern {
    #[inline]
    fn into_pattern(self) -> Pattern {
        self
    }
}

impl IntoPattern for char {
    fn into_pattern(self) -> Pattern {
        Pattern::Char(CharPattern::One(self))
    }
}

impl IntoPattern for RangeInclusive<char> {
    fn into_pattern(self) -> Pattern {
        Pattern::Char(CharPattern::RangeInclusive(self))
    }
}

impl IntoPattern for String {
    fn into_pattern(self) -> Pattern {
        Pattern::String(self)
    }
}

impl Pattern {
    pub fn multiple0(self) -> Self {
        Pattern::Multiple0(Box::new(self))
    }

    pub fn multiple1(self) -> Self {
        Pattern::Multiple1(Box::new(self))
    }

    pub fn repeat(self, n: usize) -> Self {
        Pattern::Repeat {
            pattern: Box::new(self),
            n,
        }
    }

    pub fn optional(self) -> Self {
        Pattern::Optional(Box::new(self))
    }

    pub fn match_all<'a, 's>(&'a self, text: &'s str) -> bool {
        let (ok, rest) = self.match_prefix(text);
        ok && rest.is_empty()
    }

    pub fn match_prefix<'a, 's>(&'a self, text: &'s str) -> (bool, &'s str) {
        match self {
            Pattern::Char(cp) => {
                let mut chars = text.chars();

                if chars.next().map(|c| cp.match_char(c)).unwrap_or(false) {
                    (true, chars.as_str())
                } else {
                    (false, text)
                }
            }

            Pattern::String(s) => {
                if let Some(rest) = text.strip_prefix(s) {
                    (true, rest)
                } else {
                    (false, text)
                }
            }

            Pattern::Or(patterns) => {
                for each in patterns {
                    let (ok, rest) = each.match_prefix(text);
                    if ok {
                        return (true, rest);
                    }
                }
                (false, text)
            }

            Pattern::Then(patterns) => {
                let mut rest = text;

                for each in patterns {
                    let (ok, newrest) = each.match_prefix(rest);
                    if !ok {
                        return (false, text);
                    }
                    rest = newrest;
                }

                (true, rest)
            }

            Pattern::Multiple0(pattern) => {
                let mut rest = text;
                loop {
                    let (ok, newrest) = pattern.match_prefix(rest);
                    if !ok || newrest.len() == rest.len() {
                        break;
                    }

                    rest = newrest;
                }
                (true, rest)
            }

            Pattern::Multiple1(pattern) => {
                let mut rest = text;
                let mut first = false;

                loop {
                    let (ok, newrest) = pattern.match_prefix(rest);
                    if !ok {
                        break;
                    }
                    first = true;
                    if newrest.len() == rest.len() {
                        break;
                    }
                    rest = newrest;
                }

                (first, rest)
            }

            Pattern::Optional(pattern) => {
                let (ok, rest) = pattern.match_prefix(text);
                (true, if ok { rest } else { text })
            }

            Pattern::Repeat { pattern, n } => {
                let mut rest = text;
                for _ in 0..*n {
                    let (ok, newrest) = pattern.match_prefix(rest);
                    if !ok {
                        return (false, text);
                    }
                    rest = newrest;
                }
                (true, rest)
            }
        }
    }
}
